using System;
using System.Collections;
using System.Collections.Generic;

namespace EntityKit.Runtime
{
    /// <summary>
    /// 可观察的字典类，当字典内容发生变化时会触发相应的事件
    /// </summary>
    /// <typeparam name="TKey">字典键的类型</typeparam>
    /// <typeparam name="TValue">字典值的类型</typeparam>
    public class BindableDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
    {
        private Dictionary<TKey, TValue> mDictionary = new Dictionary<TKey, TValue>();
        
        /// <summary>
        /// 当向字典中添加键值对时触发的事件
        /// </summary>
        public event Action<TKey, TValue> OnItemAdded;
        
        /// <summary>
        /// 当从字典中移除键值对时触发的事件
        /// </summary>
        public event Action<TKey, TValue> OnItemRemoved;
        
        /// <summary>
        /// 当字典中某个键的值被替换时触发的事件
        /// </summary>
        public event Action<TKey, TValue, TValue> OnItemReplaced; // key, oldValue, newValue
        
        /// <summary>
        /// 当字典被清空时触发的事件
        /// </summary>
        public event Action OnCleared;
        
        /// <summary>
        /// 当字典内容发生任何变化时触发的事件
        /// </summary>
        public event Action OnChanged;
        
        /// <summary>
        /// 获取字典中键值对的数量
        /// </summary>
        public int Count => mDictionary.Count;
        
        /// <summary>
        /// 获取或设置与指定键关联的值
        /// </summary>
        /// <param name="key">要获取或设置其值的键</param>
        /// <returns>与指定键相关联的值</returns>
        public TValue this[TKey key]
        {
            get => mDictionary[key];
            set
            {
                if (mDictionary.ContainsKey(key))
                {
                    TValue oldValue = mDictionary[key];
                    mDictionary[key] = value;
                    OnItemReplaced?.Invoke(key, oldValue, value);
                }
                else
                {
                    mDictionary[key] = value;
                    OnItemAdded?.Invoke(key, value);
                }
                OnChanged?.Invoke();
            }
        }
        
        /// <summary>
        /// 获取包含字典中所有键的集合
        /// </summary>
        public Dictionary<TKey, TValue>.KeyCollection Keys => mDictionary.Keys;
        
        /// <summary>
        /// 获取包含字典中所有值的集合
        /// </summary>
        public Dictionary<TKey, TValue>.ValueCollection Values => mDictionary.Values;
        
        /// <summary>
        /// 向字典中添加键值对
        /// </summary>
        /// <param name="key">要添加的键</param>
        /// <param name="value">要添加的值</param>
        public void Add(TKey key, TValue value)
        {
            mDictionary.Add(key, value);
            OnItemAdded?.Invoke(key, value);
            OnChanged?.Invoke();
        }
        
        /// <summary>
        /// 从字典中移除具有指定键的元素
        /// </summary>
        /// <param name="key">要移除的元素的键</param>
        /// <returns>如果成功移除元素，则为true；否则为false</returns>
        public bool Remove(TKey key)
        {
            if (mDictionary.TryGetValue(key, out TValue value))
            {
                bool removed = mDictionary.Remove(key);
                if (removed)
                {
                    OnItemRemoved?.Invoke(key, value);
                    OnChanged?.Invoke();
                }
                return removed;
            }
            return false;
        }
        
        /// <summary>
        /// 清空字典中的所有键值对
        /// </summary>
        public void Clear()
        {
            mDictionary.Clear();
            OnCleared?.Invoke();
            OnChanged?.Invoke();
        }
        
        /// <summary>
        /// 确定字典是否包含具有指定键的元素
        /// </summary>
        /// <param name="key">要在字典中定位的键</param>
        /// <returns>如果字典包含具有指定键的元素，则为true；否则为false</returns>
        public bool ContainsKey(TKey key)
        {
            return mDictionary.ContainsKey(key);
        }
        
        /// <summary>
        /// 获取与指定键关联的值
        /// </summary>
        /// <param name="key">要获取其值的键</param>
        /// <param name="value">当此方法返回时，如果找到该键，则包含与指定键相关联的值；否则包含value参数的类型的默认值</param>
        /// <returns>如果字典包含具有指定键的元素，则为true；否则为false</returns>
        public bool TryGetValue(TKey key, out TValue value)
        {
            return mDictionary.TryGetValue(key, out value);
        }
        
        /// <summary>
        /// 获取一个枚举器，用于遍历字典
        /// </summary>
        /// <returns>可用于遍历字典的枚举器</returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return mDictionary.GetEnumerator();
        }
        
        /// <summary>
        /// 获取一个枚举器，用于遍历字典
        /// </summary>
        /// <returns>可用于遍历字典的枚举器</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        
        /// <summary>
        /// 返回字典中所有键值对的字符串表示形式
        /// </summary>
        /// <returns>字典的字符串表示形式</returns>
        public override string ToString()
        {
            return $"BindableDictionary<{typeof(TKey).Name}, {typeof(TValue).Name}> (Count: {Count})";
        }
    }
}